package com.evan.idempotent.idempotent;

import com.evan.idempotent.properties.IdempotentProperties;
import com.evan.idempotent.distributedlock.DistributedLock;
import com.evan.idempotent.enums.IdempotentStorageTypeEnum;
import com.evan.idempotent.request.IdempotentRequest;
import com.evan.idempotent.storage.IdempotentStorage;
import com.evan.idempotent.storage.IdempotentStorageFactory;
import com.evan.idempotent.warpper.IdempotentRunnable;
import com.evan.idempotent.warpper.IdempotentSupplier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import java.util.concurrent.TimeUnit;

import static com.evan.idempotent.constants.IdempotentConstants.IDEMPOTENT_DEFAULT_VALUE;
import static com.evan.idempotent.constants.IdempotentConstants.LOCK_SUFFIX;


/**
 * @Description: 幂等接口实现类
 * @Author liudeguang
 */
@Slf4j
public class DistributedIdempotentImpl implements DistributedIdempotent {

    @Autowired
    private DistributedLock distributedLock;

    @Autowired
    private IdempotentProperties idempotentProperties;

    @Autowired
    private IdempotentStorageFactory idempotentStorageFactory;


    @Override
    public Boolean execute(String key, long storgeExpireTime, TimeUnit timeUnit) {
        IdempotentRequest idempotentRequest = IdempotentRequest.builder().key(key)
//                //锁的过期时间默认为存储时间，切记锁的释放时间一定要考虑好，不然业务处理时间太长了，导致锁被释放了，然后又调用了unLock()方法，就会出现错误
//                .lockExpireTime(storgeExpireTime)
                .storgeExpireTime(storgeExpireTime)
                .timeUnit(timeUnit)
                .build();
        IdempotentStorage idempotentStorage = idempotentStorageFactory.get(IdempotentStorageTypeEnum.getByCode(idempotentProperties.getStorageType()));
        return storgeExecuteWithBucket(idempotentStorage, idempotentRequest);
    }

    @Override
    public <T> T execute(String key, long lockExpireTime, long storgeExpireTime, TimeUnit timeUnit, IdempotentSupplier<T> execute, IdempotentSupplier<T> fail) {
        IdempotentRequest idempotentRequest = IdempotentRequest.builder().key(key)
                .lockExpireTime(lockExpireTime)
                .storgeExpireTime(storgeExpireTime)
                .timeUnit(timeUnit)
                .build();
        return execute(idempotentRequest, execute, fail);
    }

    @Override
    public <T> T execute(IdempotentRequest request, IdempotentSupplier<T> execute, IdempotentSupplier<T> fail) {
        return distributedLock.lock(request.getKey() + LOCK_SUFFIX, request.getStorgeExpireTime(), request.getTimeUnit(),
                (k, ex) -> {
                    IdempotentStorage idempotentStorage = idempotentStorageFactory.get(IdempotentStorageTypeEnum.getByCode(idempotentProperties.getStorageType()));
                    return storgeExecute(idempotentStorage, request, execute, fail);
                }, fail);
    }

    @Override
    public void execute(String key, long lockExpireTime, long storgeExpireTime, TimeUnit timeUnit, IdempotentRunnable execute, IdempotentRunnable fail) {
        IdempotentRequest idempotentRequest = IdempotentRequest.builder().key(key)
                .lockExpireTime(lockExpireTime)
                .storgeExpireTime(storgeExpireTime)
                .timeUnit(timeUnit)
                .build();
        execute(idempotentRequest, execute, fail);
    }

    @Override
    public void execute(IdempotentRequest request, IdempotentRunnable execute, IdempotentRunnable fail) {
        distributedLock.lock(request.getKey() + LOCK_SUFFIX, request.getStorgeExpireTime(), request.getTimeUnit(), (k, ex) -> {
            IdempotentStorage idempotentStorage = idempotentStorageFactory.get(IdempotentStorageTypeEnum.getByCode(idempotentProperties.getStorageType()));
            storgeExecute(idempotentStorage, request, execute, fail);
        }, fail);
    }

    /**
     * 执行校验幂等
     *
     * @param idempotentStorage
     * @param request
     * @param execute
     * @param fail
     * @param <T>
     * @return
     */
    private <T> T storgeExecute(IdempotentStorage idempotentStorage, IdempotentRequest request, Object execute, Object fail) {
        String storgeValue = idempotentStorage.getValue(request.getKey());
        // 存储中有数据,则不能继续往下执行
        if (!StringUtils.hasText(storgeValue)) {
            // 先执行业务逻辑，因为当前是在加锁状态的，不用担心。
            T executeResult = getExecuteResult(execute, request);
            // 存入缓存
            idempotentStorage.setValue(request.getKey(), IDEMPOTENT_DEFAULT_VALUE, request.getStorgeExpireTime(), request.getTimeUnit());
            return executeResult;
        }
        // 不能继续往下执行
        return getExecuteResult(fail, request);
    }

    /**
     * 锁内进行验证是否存在
     *
     * @param idempotentStorage
     * @param request
     * @return
     */
    private Boolean storgeExecuteWithBucket(IdempotentStorage idempotentStorage, IdempotentRequest request) {
        return idempotentStorage.verify(request.getKey(), request.getStorgeExpireTime(), request.getTimeUnit());
    }

    /**
     * 获得执行结果 单个的执行器
     *
     * @param execute 执行器
     * @param request 请求
     * @param <T>
     * @return
     */
    private <T> T getExecuteResult(Object execute, IdempotentRequest request) {
        T executeResult = null;
        if (execute instanceof IdempotentSupplier) {
            IdempotentSupplier<T> idempotentSupplier = (IdempotentSupplier<T>) execute;
            executeResult = idempotentSupplier.get(request.getKey(), null);
        } else {
            IdempotentRunnable idempotentRunnable = (IdempotentRunnable) execute;
            idempotentRunnable.run(request.getKey(), null);
        }
        return executeResult;
    }

}
